﻿Imports System.Text
Imports VsSpeakerCompanion
Imports System.ComponentModel.Composition
Imports Microsoft.VisualStudio.Text.Editor
Imports Microsoft.VisualStudio.Text.Classification
Imports TwitterAPI
Imports System.Windows.Threading
Imports System.Windows.Input
Imports System.Windows.Media.Animation
Imports System.Windows
Imports System.Windows.Media
Imports System.Windows.Controls
Imports VsSpeakerCompanion.UI
Imports Microsoft.VisualStudio.Shell.Interop
Imports Microsoft.VisualStudio

Namespace FontSizeWidget
    ''' <summary>
    ''' Allows the audience to send feedback about the currently used font size
    ''' (e.g. the usual "I cant read the screen from the back, font is too small!")
    ''' </summary>
    Public Class FontSizeWidget
        Implements IWidget
        ' couple of lists to keep track of received feedback
        Private bigVoters As List(Of String)
        Private smallVoters As List(Of String)

        ' our UI
        Private WithEvents fontSize As FontSize

        ' we will use this service to animate our widget when trying to get the speaker's attention
        Private widgetAnimationService As IWidgetAnimationService

        ' we need this service to access some core VS functionality
        Private visualStudioService As IVisualStudioService

        ' and we also need a service provider
        Private serviceProvider As IServiceProvider

        Private editorFormatMapService As IEditorFormatMapService

        Public Sub New(ByVal visualStudioService As IVisualStudioService, ByVal editorFormatMapService As IEditorFormatMapService, _
                       ByVal twitterService As TwitterService, ByVal widgetAnimationService As IWidgetAnimationService, ByVal serviceProvider As IServiceProvider)
            Me.visualStudioService = visualStudioService
            Me.serviceProvider = serviceProvider
            Me.editorFormatMapService = editorFormatMapService
            Me.widgetAnimationService = widgetAnimationService
            fontSize = New FontSize

            bigVoters = New List(Of String)
            smallVoters = New List(Of String)

            ' register the two feedback messages this widget understands
            twitterService.RegisterCommand("font too small", AddressOf ProcessSmallMessages)
            twitterService.RegisterCommand("font too big", AddressOf ProcessBigMessages)
        End Sub

        ''' <summary>
        ''' Keep track of people thinking the font size is too small
        ''' </summary>
        ''' <param name="feedItems"></param>
        Private Sub ProcessSmallMessages(ByVal feedItems As IEnumerable(Of FeedItem))
            ' people has only a single vote, but they can change its value anytime
            feedItems.ToList().ForEach(Sub(item)
                                           If bigVoters.Contains(item.User) Then
                                               bigVoters.Remove(item.User)
                                           End If
                                       End Sub)
            feedItems.ToList().ForEach(Sub(item)
                                           If Not smallVoters.Contains(item.User) Then
                                               smallVoters.Add(item.User)
                                           End If
                                       End Sub)

            ' update text and animation based on new feedback received
            UpdateText()
            UpdateAnimation()
        End Sub

        Private Sub UpdateAnimation()
            widgetAnimationService.Start(Me)
        End Sub

        ''' <summary>
        ''' Keep track of people thinking the font size is too big
        ''' </summary>
        ''' <param name="feedItems"></param>
        Private Sub ProcessBigMessages(ByVal feedItems As IEnumerable(Of FeedItem))
            ' people has only a single vote, but they can change its value anytime
            feedItems.ToList().ForEach(Sub(item)
                                           If smallVoters.Contains(item.User) Then
                                               smallVoters.Remove(item.User)
                                           End If
                                       End Sub)
            feedItems.ToList().ForEach(Sub(item)
                                           If Not bigVoters.Contains(item.User) Then
                                               bigVoters.Add(item.User)
                                           End If
                                       End Sub)

            ' update text and animation based on new feedback received
            UpdateText()
            UpdateAnimation()
        End Sub

        Private Sub UpdateText()
            Dim voteTotal = bigVoters.Count + smallVoters.Count
            Dim fontTooBigPerc = Convert.ToDouble(bigVoters.Count) / Convert.ToDouble(voteTotal)

            If 0.4 < fontTooBigPerc AndAlso fontTooBigPerc < 0.6 Then
                ' TODO: move to resource to ease localization
                fontSize.Advice = "perfect ;)"
            ElseIf fontTooBigPerc <= 0.4 Then
                ' TODO: move to resource to ease localization
                fontSize.Advice = "too small!"
            Else
                ' TODO: move to resource to ease localization
                fontSize.Advice = "too big!"
            End If
        End Sub

        ''' <summary>
        ''' Clicking on our widget means the speaker acknowledged the received feedback about the font size being too small or too big
        ''' so we should stop the animation!
        ''' </summary>
        ''' <param name="source"></param>
        ''' <param name="e"></param>
        Private Sub fontSize_MouseDown() Handles fontSize.MouseDown
            widgetAnimationService.Stop(Me)
        End Sub

        ''' <summary>
        ''' Do not show any text when the audience is happy with the font size
        ''' </summary>
        Private Sub ResetAdvice()
            ' recreate lists
            bigVoters = New List(Of String)
            smallVoters = New List(Of String)
            ' text should be blank
            fontSize.Advice = ""
        End Sub

        ''' <summary>
        ''' Handle the increasing of the current font size
        ''' </summary>
        ''' <param name="source"></param>
        ''' <param name="e"></param>
        Private Sub fontSize_BigAClicked() Handles fontSize.BigAClicked
            ' if an animation is running we should stop it now
            widgetAnimationService.Stop(Me)

            ' set 50 as a safe limit on font size
            If fontSize.Size < 50 Then
                ' increase the  font size
                fontSize.Size += 1
                ' set the new font size
                ReformatTextView(visualStudioService.GetActiveTextView(), fontSize.Size)
            End If

            ' changing the font size should reset the 'too small' or 'too big' legend
            ResetAdvice()
        End Sub

        ''' <summary>
        ''' Handle the decreasing of the current font size
        ''' </summary>
        ''' <param name="source"></param>
        ''' <param name="e"></param>
        Private Sub fontSize_SmallAClicked() Handles fontSize.SmallAClicked
            widgetAnimationService.Stop(Me)

            ' dont go lower than one!
            If fontSize.Size > 1 Then
                ' decrease font size by 1
                fontSize.Size -= 1
                ' set the new font size
                ReformatTextView(visualStudioService.GetActiveTextView(), fontSize.Size)
            End If

            ' changing the font size should reset the 'too small' or 'too big' legend
            ResetAdvice()
        End Sub

        ''' <summary>
        ''' Reset our widget data to its original state
        ''' </summary>
        Public Sub Reset() Implements IWidget.Reset
            fontSize.Size = GetCurrentTextSize(visualStudioService.GetActiveTextView())
            ResetAdvice()
        End Sub

        Public ReadOnly Property VisualElement As System.Windows.UIElement Implements IWidget.VisualElement
            Get
                ' this is our widget UI
                Return fontSize
            End Get
        End Property

        ''' <summary>
        ''' Retrieves the current font size of a given text view
        ''' NOTE: The new managed editor doesn't provide just yet an easier way to do this
        ''' that's why we needed to implement this retrieving VS services, etc.
        ''' This may change in a future release of VS 2010
        ''' </summary>
        ''' <param name="textView"></param>
        ''' <returns></returns>
        Public Function GetCurrentTextSize(ByVal textView As IWpfTextView) As Double
            If textView IsNot Nothing Then
                Try
                    Dim guidTextEditorFontCategory As New Guid("A27B4E24-A735-4d1d-B8E7-9716E1E3D8E0")
                    Dim vsHelper As New VisualStudioHelper(textView.TextBuffer)
                    Dim storage = TryCast(Me.serviceProvider.GetService(GetType(SVsFontAndColorStorage)), IVsFontAndColorStorage)
                    If storage IsNot Nothing Then
                        Dim fontInfo = {New FontInfo}

                        If ErrorHandler.Succeeded(storage.OpenCategory(guidTextEditorFontCategory, CUInt(__FCSTORAGEFLAGS.FCSF_LOADDEFAULTS))) Then
                            If ErrorHandler.Succeeded(storage.GetFont(Nothing, fontInfo)) Then
                                If fontInfo IsNot Nothing AndAlso fontInfo.Count() > 0 Then
                                    Return CDbl(fontInfo(0).wPointSize)
                                End If
                            End If
                            storage.CloseCategory()
                        End If
                    End If
                Catch
                    ' TODO: log the exception
                End Try
            End If

            ' if something fails just use a default size of 10
            Return 10
        End Function

        ''' <summary>
        ''' Set new font size
        ''' NOTE: The new managed editor doesn't provide just yet an easier way to do this
        ''' that's why we needed to implement this retrieving VS services, etc.
        ''' This may change in a future release of VS 2010
        ''' </summary>
        ''' <param name="textView"></param>
        ''' <param name="size"></param>
        Public Sub ReformatTextView(ByVal textView As IWpfTextView, ByVal size As Double)
            Try
                Dim guidTextEditorFontCategory As New Guid("A27B4E24-A735-4d1d-B8E7-9716E1E3D8E0")

                Dim vsHelper As New VisualStudioHelper(textView.TextBuffer)

                Dim storage = TryCast(Me.serviceProvider.GetService(GetType(SVsFontAndColorStorage)), IVsFontAndColorStorage)
                If storage IsNot Nothing Then
                    Dim fontInfo = {New FontInfo}

                    If ErrorHandler.Succeeded(storage.OpenCategory(guidTextEditorFontCategory, CUInt(__FCSTORAGEFLAGS.FCSF_LOADDEFAULTS Or __FCSTORAGEFLAGS.FCSF_PROPAGATECHANGES))) Then
                        If ErrorHandler.Succeeded(storage.GetFont(Nothing, fontInfo)) Then
                            If fontInfo IsNot Nothing AndAlso fontInfo.Count() > 0 Then
                                fontInfo(0).wPointSize = CUShort(size)
                                storage.SetFont(fontInfo)
                            End If
                        End If
                        storage.CloseCategory()
                    End If
                End If
            Catch
                ' TODO: log the exception
            End Try
        End Sub
    End Class
End Namespace
